/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Add TSOL banner, trailer, page header/footers to a print job
 */

/* system header files */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <limits.h>
#include <errno.h>
#include <signal.h>
#include <locale.h>
#include <tsol/label.h>

/* typedefs */

typedef int BOOL;

/* constants */

#ifndef FALSE
#define	FALSE 0
#endif
#ifndef TRUE
#define	TRUE 1
#endif

#define	ME "lp.tsol_separator"
#define	POSTSCRIPTLIB "/usr/lib/lp/postscript"
#define	SEPARATORPS "tsol_separator.ps"
#define	BANNERPS "tsol_banner.ps"
#define	TRAILERPS "tsol_trailer.ps"
#define	MAXUSERLEN 32
#define	MAXHOSTLEN 32

/* external variables */

int	optind;			/* Used by getopt */
char    *optarg;		/* Used by getopt */

/* prototypes for static functions */

static int ProcessArgs(int argc, char **argv);
static void Usage(void);
static void ParseUsername(char *input, char *user, char *host);
static void EmitPSFile(const char *name);
static BOOL EmitFile(FILE *file);
static void EmitJobData(void);
static void EmitPrologue(void);
static void EmitCommandLineInfo(void);
static void EmitClockBasedInfo(void);
static void EmitLabelInfo(void);
static void CopyStdin(void);

/* static variables */

static char *ArgSeparatorPS;
static char *ArgBannerPS;
static char *ArgTrailerPS;
static char *ArgPSLib;
static char *ArgPrinter;
static char *ArgJobID;
static char *ArgUser;
static char *ArgTitle;
static char *ArgFile;
static BOOL ArgReverse;
static BOOL ArgNoPageLabels;
static int ArgDebugLevel;
static FILE *ArgLogFile;
static m_label_t *FileLabel;
static char *remoteLabel;

int
main(int argc, char *argv[])
{
	int	err;
	/*
	 * Run immune from typical interruptions, so that
	 * we stand a chance to get the fault message.
	 * EOF (or startup error) is the only way out.
	 */
	(void) signal(SIGHUP, SIG_IGN);
	(void) signal(SIGINT, SIG_IGN);
	(void) signal(SIGQUIT, SIG_IGN);
	(void) signal(SIGTERM, SIG_IGN);

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	if (ProcessArgs(argc, argv) != 0)
		exit(1);

	if ((FileLabel = m_label_alloc(MAC_LABEL)) == NULL)
		exit(1);
	/*
	 * If the job was submitted via remotely, the label of the
	 * remote peer will be set in the SLABEL environment variable
	 * by copying it out of the SECURE structure.
	 *
	 * If there is no SLABEL value, the job was submitted locally
	 * via the named pipe, and the file label can be determined
	 * from its pathname.
	 */
	if ((remoteLabel = getenv("SLABEL")) != NULL) {
		m_label_free(FileLabel);
		FileLabel = NULL;
		if (str_to_label(remoteLabel, &FileLabel, MAC_LABEL,
		    L_NO_CORRECTION, &err) == -1) {
			perror("str_to_label");
			exit(1);
		}
	} else if (getlabel(ArgFile, FileLabel) != 0) {
		(void) fprintf(ArgLogFile,
		    gettext("%1$s: cannot get label of %2$s: %3$s\n"),
		    ME, ArgFile, strerror(errno));
		exit(1);
	}

	/* All of these functions exit if they encounter an error */
	EmitJobData();
	EmitPSFile(ArgSeparatorPS);
	if (ArgReverse)
		EmitPSFile(ArgTrailerPS);
	else
		EmitPSFile(ArgBannerPS);
	CopyStdin();
	if (ArgReverse)
		EmitPSFile(ArgBannerPS);
	else
		EmitPSFile(ArgTrailerPS);
	if (ArgDebugLevel >= 1)
		(void) fprintf(ArgLogFile, gettext("Done.\n"));
	m_label_free(FileLabel);
	return (0);
}

static void
EmitJobData(void)
{
	EmitPrologue();
	EmitCommandLineInfo();
	EmitClockBasedInfo();
	EmitLabelInfo();

	/* Emit ending PostScript code */
	(void) printf("end\n\n");
	(void) printf("%%%% End of code generated by lp.tsol_separator\n\n");

}

static void
EmitPrologue(void)
{
	/* Emit preliminary PostScript code */
	(void) printf("%%!\n\n");
	(void) printf("%%%% Begin code generated by lp.tsol_separator\n\n");

	(void) printf("%%%% Create JobDict if it doesn't exist\n");
	(void) printf("userdict /JobDict known not {\n");
	(void) printf("  userdict /JobDict 100 dict put\n");
	(void) printf("} if\n\n");

	(void) printf("%%%% Define job parameters, including TSOL security "
	    "info\n");
	(void) printf("JobDict\n");
	(void) printf("begin\n");
}

/* Emit parameters obtained from command line options */

static void
EmitCommandLineInfo(void)
{
	char user[MAXUSERLEN + 1];
	char host[MAXHOSTLEN + 1];

	(void) printf("\t/Job_Printer (%s) def\n", ArgPrinter);
	ParseUsername(ArgUser, user, host);
	(void) printf("\t/Job_Host (%s) def\n", host);
	(void) printf("\t/Job_User (%s) def\n", user);
	(void) printf("\t/Job_JobID (%s) def\n", ArgJobID);
	(void) printf("\t/Job_Title (%s) def\n", ArgTitle);
	(void) printf("\t/Job_DoPageLabels (%s) def\n",
	    ArgNoPageLabels ? "NO" : "YES");
	(void) printf("\n");
}

/* Emit parameters generated from the system clock */

static void
EmitClockBasedInfo(void)
{
	char timebuf[80];
	struct timeval clockval;

	(void) gettimeofday(&clockval, NULL);
	(void) strftime(timebuf, sizeof (timebuf), NULL,
	    localtime(&clockval.tv_sec));
	(void) printf("\t/Job_Date (%s) def\n", timebuf);
	(void) printf("\t/Job_Hash (%ld) def\n", clockval.tv_usec % 100000L);
	(void) printf("\n");
}

/* Emit parameters derived from the SL and IL of the file being printed. */

static void
EmitLabelInfo(void)
{
	char	*header = NULL;		/* DIA banner page fields */
	char	*label = NULL;
	char	*caveats = NULL;
	char	*channels = NULL;
	char	*page_label = NULL;	/* interior pages label */

	if (label_to_str(FileLabel, &header, PRINTER_TOP_BOTTOM,
	    DEF_NAMES) != 0) {
		(void) fprintf(ArgLogFile,
		    gettext("%s: label_to_str PRINTER_TOP_BOTTOM: %s.\n"),
		    ME, strerror(errno));
		exit(1);
	}
	if (label_to_str(FileLabel, &label, PRINTER_LABEL,
	    DEF_NAMES) != 0) {
		(void) fprintf(ArgLogFile,
		    gettext("%s: label_to_str PRINTER_LABEL: %s.\n"),
		    ME, strerror(errno));
		exit(1);
	}
	if (label_to_str(FileLabel, &caveats, PRINTER_CAVEATS,
	    DEF_NAMES) != 0) {
		(void) fprintf(ArgLogFile,
		    gettext("%s: label_to_str PRINTER_CAVEATS: %s.\n"),
		    ME, strerror(errno));
		exit(1);
	}
	if (label_to_str(FileLabel, &channels, PRINTER_CHANNELS,
	    DEF_NAMES) != 0) {
		(void) fprintf(ArgLogFile,
		    gettext("%s: label_to_str PRINTER_CHANNELS: %s.\n"),
		    ME, strerror(errno));
		exit(1);
	}
	if (label_to_str(FileLabel, &page_label, M_LABEL,
	    LONG_NAMES) != 0) {
		(void) fprintf(ArgLogFile,
		    gettext("%s: label_to_str M_LABEL: %s.\n"),
		    ME, strerror(errno));
		exit(1);
	}

	(void) printf("\t/Job_Classification (%s) def\n", header);
	(void) printf("\t/Job_Protect (%s) def\n", label);
	(void) printf("\t/Job_Caveats (%s) def\n", caveats);
	(void) printf("\t/Job_Channels (%s) def\n", channels);
	(void) printf("\t/Job_SL_Internal (%s) def\n", page_label);

	/* Free memory allocated label_to_str */
	free(header);
	free(label);
	free(caveats);
	free(channels);
	free(page_label);
}

/*
 * Parse input "host!user" to separate host and user names.
 */

static void
ParseUsername(char *input, char *user, char *host)
{
	char *cp;

	if ((cp = strchr(input, '@')) != NULL) {
		/* user@host */
		(void) strlcpy(host, cp + 1, MAXHOSTLEN + 1);
		*cp = '\0';
		(void) strlcpy(user, input, MAXUSERLEN + 1);
		*cp = '@';
	} else if ((cp = strchr(input, '!')) != NULL) {
		/* host!user */
		(void) strlcpy(user, cp + 1, MAXUSERLEN + 1);
		*cp = '\0';
		(void) strlcpy(host, input, MAXHOSTLEN + 1);
		*cp = '!';
	} else {
		/* user */
		(void) strlcpy(user, input, MAXUSERLEN + 1);
		host[0] = '\0';
	}
}


static void
CopyStdin(void)
{
	if (!EmitFile(stdin)) {
		(void) fprintf(ArgLogFile,
		    gettext("%s: Error copying stdin to stdout\n"), ME);
		exit(1);
	}
}


static BOOL
EmitFile(FILE *file)
{
	int len;
#define	BUFLEN 1024
	char buf[BUFLEN];

	while ((len = fread(buf, 1, BUFLEN, file)) > 0) {
		if (fwrite(buf, 1, len, stdout) != len)
			return (FALSE);
	}
	if (!feof(file))
		return (FALSE);
	return (TRUE);
}


static void
EmitPSFile(const char *name)
{
	char path[PATH_MAX];
	FILE *file;
	BOOL emitted;

	if (name[0] != '/') {
		(void) strlcpy(path, ArgPSLib, sizeof (path));
		(void) strlcat(path, "/", sizeof (path));
		(void) strlcat(path, name, sizeof (path));
	} else {
		(void) strlcpy(path, name, sizeof (path));
	}

	file = fopen(path, "r");
	if (file == NULL) {
		(void) fprintf(ArgLogFile,
		    gettext("%s: Error opening PostScript file %s. %s.\n"),
		    ME, path, strerror(errno));
		exit(1);
	}

	emitted = EmitFile(file);
	(void) fclose(file);
	if (!emitted) {
		(void) fprintf(ArgLogFile, gettext(
		    "%s: Error copying PostScript file %s to stdout.\n"),
		    ME, path);
		exit(1);
	}
}


static int
ProcessArgs(int argc, char *argv[])
{
	int	option_letter;
	char	*options_string = "lrd:e:s:b:t:L:";

	/* set default values for arguments */
	ArgSeparatorPS = SEPARATORPS;
	ArgBannerPS = BANNERPS;
	ArgTrailerPS = TRAILERPS;
	ArgPSLib = POSTSCRIPTLIB;
	ArgNoPageLabels = ArgReverse = FALSE;
	ArgDebugLevel = 0;
	ArgLogFile = stderr;

	/* read switch arguments once to get error log file */
	while ((option_letter = getopt(argc, argv, options_string)) != EOF) {
		switch (option_letter) {
		case 'd':
			ArgDebugLevel = atoi(optarg);
			break;
		case 'e':
			ArgLogFile = fopen(optarg, "a");
			if (ArgLogFile == NULL) {
				(void) fprintf(stderr,
				    gettext("Cannot open log file %s\n"),
				    optarg);
				return (-1);
			}
			break;
		case '?':	/* ? or unrecognized option */
			Usage();
			return (-1);
		}
	}

	if (ArgDebugLevel > 0)
		(void) fprintf(ArgLogFile,
		    gettext("Processing switch arguments\n"));

	/* re-read switch arguments */
	optind = 1;
	while ((option_letter = getopt(argc, argv, options_string)) != EOF) {
		switch (option_letter) {
		case 'd':
			ArgDebugLevel = atoi(optarg);
			break;
		case 'e':
			/* This was handled in earlier pass through args */
			break;
		case 'l':
			ArgNoPageLabels = TRUE;
			break;
		case 'r':
			ArgReverse = TRUE;
			break;
		case 's':
			ArgSeparatorPS = optarg;
			break;
		case 'b':
			ArgBannerPS = optarg;
			break;
		case 't':
			ArgTrailerPS = optarg;
			break;
		case 'L':
			ArgPSLib = optarg;
			break;
		case '?':	/* ? or unrecognized option */
			Usage();
			return (-1);
		}
	}

	/* Adjust arguments to skip over options */
	argc -= optind;		/* Number of remaining(non-switch) args */
	argv += optind;		/* argv[0] is first(non-switch) args */

	if (argc != 5) {
		(void) fprintf(ArgLogFile,
		    gettext("Wrong number of arguments.\n\n"));
		Usage();
		return (-1);
	}

	ArgPrinter = argv++[0];
	ArgJobID = argv++[0];
	ArgUser = argv++[0];
	ArgTitle = argv++[0];
	ArgFile = argv++[0];

	if (ArgDebugLevel >= 1) {
		(void) fprintf(ArgLogFile, gettext("Arguments processed\n"));
		(void) fprintf(ArgLogFile, gettext("Printer: %s\n"),
		    ArgPrinter);
		(void) fprintf(ArgLogFile, gettext("Job ID: %s\n"), ArgJobID);
		(void) fprintf(ArgLogFile, gettext("User: %s\n"), ArgUser);
		(void) fprintf(ArgLogFile, gettext("Title: %s\n"), ArgTitle);
		(void) fprintf(ArgLogFile, gettext("File: %s\n"), ArgFile);
	}

	return (0);
}


static void
Usage(void)
{
	static const char *OPTFMT = "    %-8s %-9s %s\n";

	(void) fprintf(ArgLogFile,
	    gettext("Usage:  lp.tsol_separator [OPTIONS] %s\n"),
	    gettext("PRINTER JOBID HOST!USER TITLE FILE"));
	(void) fprintf(ArgLogFile, gettext("  OPTIONS:\n"));
	(void) fprintf(ArgLogFile, OPTFMT, "-r", gettext("Reverse"),
	    gettext("Reverse banner/trailer order"));
	(void) fprintf(ArgLogFile, OPTFMT, "-l", gettext("Labels"),
	    gettext("Suppress page header/footer labels"));
	(void) fprintf(ArgLogFile, OPTFMT, gettext("-b FILE"),
	    gettext("Banner"),
	    gettext("PostScript program for banner (default tsol_banner.ps)"));
	(void) fprintf(ArgLogFile, OPTFMT, gettext("-s FILE"),
	    gettext("Separator"),
	    gettext("PostScript program for separator "
	    "(default tsol_separator.ps)"));
	(void) fprintf(ArgLogFile, OPTFMT, gettext("-t FILE"),
	    gettext("Trailer"),
	    gettext("PostScript program for trailer "
	    "(default tsol_trailer.ps)"));
	(void) fprintf(ArgLogFile, OPTFMT, gettext("-L DIR"),
	    gettext("Library"),
	    gettext("Directory to search for PostScript programs"));
	(void) fprintf(ArgLogFile, OPTFMT, "", "",
	    gettext("(default /usr/lib/lp/postscript)"));
	(void) fprintf(ArgLogFile, OPTFMT, gettext("-d N"), gettext("Debug"),
	    gettext("Set debug level to N"));
	(void) fprintf(ArgLogFile, OPTFMT, gettext("-e FILE"),
	    gettext("Error File"),
	    gettext("Append error and debugging output to FILE"));
}
